/************* Global Objects *************/
var wManager = null;
var wList = null;
var wAddControl = null;
var selectedDefinition = null;
var storage = null;

var WordEntryStates = {
    LEARNING: 1,
    LEARNT: 2,
    DELETED: 3,
};

var WordLearningProgress = {
    MIN_VALUE: 1,
    MAX_VALUE: 5,
};

/************* Utility functions *************/
function copyElementFromTemplate(templateId, newId){
    var newWordListItem = ($(templateId)).cloneNode(true);
    newWordListItem.id = newId;
    
    newWordListItem.removeClassName("notDisplayed");
    return newWordListItem;
}

function we(id){
    return wManager.entries.get(id);
}


/************* Application classes *************/
/** 
 * The data structure and behavior of a word with its definition
 */
var WordEntry = Class.create();

WordEntry.prototype = {
    id: null,
    word: null,
    definition: null,
    progress: WordLearningProgress.MIN_VALUE,
    state: WordEntryStates.LEARNING,
    
    // since Javscript does not support method overloading, I had to improvize
    initialize: function(w, d, p, s){
        if (d == undefined) {
            this.initializeByAWordEntryPrototype(w);
        }
        else {
            if (p == undefined) {
                this.initializeByWordAndDefinition(w, d);
            }
            else {
                if (s == undefined) {
                    this.initializeByWordAndDefinitionAndProgress(w, d, p);
                }
                else {
                    this.initializeByWordAndDefinitionAndProgressAndState(w, d, p, s);
                }
            }
        }
    },
    
    initializeByWordAndDefinition: function(word, definition){
        this.setWord(word);
        this.setDefinition(definition);
    },
    
    initializeByWordAndDefinitionAndProgress: function(word, definition, progress){
        this.initializeByWordAndDefinition(word, definition);
        this.setProgress(progress);
    },
    
    initializeByWordAndDefinitionAndProgressAndState: function(word, definition, progress, state){
        this.initializeByWordAndDefinitionAndProgress(word, definition, progress);
        this.setState(state);
    },
    
    // constructs from another object that only has the field structure, no methods
    initializeByAWordEntryPrototype: function(aWordEntryPrototype){
        this.initializeByWordAndDefinitionAndProgressAndState(aWordEntryPrototype.word, aWordEntryPrototype.definition, aWordEntryPrototype.progress, aWordEntryPrototype.state);
    },
    
    setId: function(id){
        this.id = id;
    },
    
    setWord: function(word){
        this.word = word;
    },
    
    setDefinition: function(definition){
        this.definition = definition;
    },
    
    setProgress: function(progress){
        progress = parseInt(progress);
        
        // normalize to minimum progress value
        if (progress < WordLearningProgress.MIN_VALUE || isNaN(progress)) {
            this.progress = WordLearningProgress.MIN_VALUE;
            return;
        }
        // normalize to maximum progress value
        if (progress > WordLearningProgress.MAX_VALUE) {
            this.progress = WordLearningProgress.MAX_VALUE;
            return;
        }
        // if not returned yet, set the value
        this.progress = progress;
    },
    
    setState: function(state){
        switch (state) {
            case WordEntryStates.LEARNING:
            case WordEntryStates.LEARNT:
            case WordEntryStates.DELETED:{
                this.state = state;
                break;
            }
            default:
                {
                    this.state = WordEntryStates.LEARNING;
                    break;
                }
        }
    },
}

/** 
 * Holds the client-side application logic and data (list of words with definitions)
 */
var WordManager = Class.create();

WordManager.prototype = {
    entries: null,
    
    initialize: function(){
        this.entries = new Hash();
    },
    
    addWordEntry: function(wordEntry){
        // update the entries
        newId = this.entries.size() + 1;
        wordEntry.setId(newId);
        this.entries.set(newId, wordEntry);
        
        // also synchronize with the HTML UI
        wList.handleWordAdded(newId);
    },
    
    deleteWordEntry: function(id){
        this.entries.unset(id);
    },
    
    updateWordEntry: function(id, wordEntry){
        // update the entries
        this.entries.set(id, wordEntry);
        
        // also synchronize with the HTML UI
        wList.handleWordUpdated(id);
    },
    
    cleanup: function(){
        this.entries = new Hash();
        wList.cleanup();
    },
}


/**
 * Handles the HTML element that contains the list of words with definitions
 */
var WordList = Class.create();

WordList.prototype = {
    items: null,
    listElem: null,
    
    initialize: function(){
        this.items = new Hash();
        this.listElem = $("wordList");
    },
    
    addItem: function(aWordListItem){
        //add the needed html to the needed place
        if (!this.items.get(aWordListItem.id)) {
            this.items.set(aWordListItem.id, aWordListItem);
            this.listElem.appendChild(aWordListItem.itemElem);
        }
    },
    
    removeItem: function(id){
        this.items.unset(id);
        Element.hide("wordListItem" + id);
    },
    
    cleanup: function(){
        listElemChild = $("wordListItemTemplate").nextSibling;
        while ($("wordListItemTemplate").nextSibling) {
            this.listElem.removeChild($("wordListItemTemplate").nextSibling);
        }
        this.items = new Hash();
        ;
    },
    
    getItem: function(id){
        return this.items.get(id)
    },
    
    handleWordAdded: function(weId){
        var aWordListItem = new WordListItem(wManager.entries.get(weId));
        this.addItem(aWordListItem);
        new Draggable(aWordListItem.itemElem, {
            revert: true
        });
    },
    
    handleWordDeleted: function(weId){
        this.removeItem(weId);
    },
    
    handleWordUpdated: function(weId){
        this.items.get(weId).setWord(we(weId).word);
    },
}

/**
 * Handles the HTML element that displays the list of words with definitions
 */
var WordListItem = Class.create();

WordListItem.prototype = {
    id: null, //this has to be null, so that we get an error if it is not assigned
    itemElem: null,
    wordElem: null,
    progressImgElem: null,
    
    initialize: function(aWordEntry){
        this.id = aWordEntry.id;
        
        this.itemElem = copyElementFromTemplate("wordListItemTemplate", "wordListItem" + this.id);
        
        this.wordElem = this.itemElem.getElementsByClassName("word")[0];
        this.progressImgElem = this.itemElem.getElementsByClassName("progressImg")[0];
        
        this.wordElem.update(aWordEntry.word);
        this.adjustProgressImage(aWordEntry.progress);
        
        this.itemElem.getElementsByClassName("btnShowDefinition")[0].observe("click", this.showDefinition.bindAsEventListener(this));
        
    },
    
    showDefinition: function(){
        selectedDefinition.update(this.id);
        selectedDefinition.show(true);
    },
    
    adjustProgressImage: function(progress){
        progress = parseInt(progress);
        if (progress >= WordLearningProgress.MIN_VALUE && progress <= WordLearningProgress.MAX_VALUE) {
            this.progressImgElem.src = "img/progress" + progress + ".png";
        }
    },
    
    setWord: function(word){
        this.wordElem.update(word);
    },
}


/**
 * Handles the HTML element responsible for displaying the definition along with guessing controls for the selected word
 */
var SelectedDefinition = Class.create();

SelectedDefinition.prototype = {
    id: 0,
    wordElem: null,
    definitionElem: null,
    
    initialize: function(){
        this.id = 0;
        //html bindings
        this.wordElem = $("selectedDefinitionWord");
        this.definitionElem = $("selectedDefinitionText");
        
        //event handler bindings
        $("selectedDefinitionBtnGuessed").observe("click", this.handleWordGuessed.bindAsEventListener(this, true));
        $("selectedDefinitionBtnNotGuessed").observe("click", this.handleWordGuessed.bindAsEventListener(this, false));
        
        //
        this.setWordEditable();
        this.setDefinitionEditable();
    },
    
    setWordEditable: function(){
        this.wordEditor = new Ajax.InPlaceEditor(this.wordElem, 'putUrlHereIfServerProcessingNeeded.html', {
            callback: this.updateWordCallback.bind(this)
        });
    },
    
    setDefinitionEditable: function(){
        this.definitionEditor = new Ajax.InPlaceEditor(this.definitionElem, 'putUrlHereIfServerProcessingNeeded.html', {
            callback: this.updateMessageCallback.bind(this),
            rows: 5
        });
    },
    
    cancelAllEdits: function(){
        this.wordEditor.leaveEditMode();
        this.definitionEditor.leaveEditMode();
    },
    
    updateWordCallback: function(form, value){
        value = value.escapeHTML();
        var aWordEntry = we(this.id);
        aWordEntry.setWord(value);
        wManager.updateWordEntry(this.id, aWordEntry);
        this.wordElem.update(value);
        this.setWordEditable();
    },
    
    updateMessageCallback: function(form, value){
        value = value.escapeHTML();
        var aWordEntry = wManager.entries.get(this.id);
        aWordEntry.setDefinition(value);
        wManager.updateWordEntry(this.id, aWordEntry);
        this.definitionElem.update(value);
        this.setDefinitionEditable();
    },
    
    update: function(weId){
        var aWordEntry = wManager.entries.get(weId);
        
        this.id = aWordEntry.id;
        this.wordElem.update(aWordEntry.word);
        this.definitionElem.update(aWordEntry.definition);
        
    },
    
    handleWordGuessed: function(sender, isGuessed){
        var aWordEntry = wManager.entries.get(this.id);
        aWordEntry.setProgress(aWordEntry.progress + (isGuessed ? 1 : -1));
        
        wList.getItem(this.id).adjustProgressImage(aWordEntry.progress);
        
        selectedDefinition.show(false);
        this.cancelAllEdits();
    },
    
    show: function(isShown){
        if (isShown) {
            $("overlay").show();
            $("selectedDefinition").show();
            
            $("selectedDefinition").style.top = Element.cumulativeOffset($("wordList")).top + ($("wordList").getHeight() - $("selectedDefinition").getHeight()) / 2 + "px";
            $("selectedDefinition").style.left = Element.cumulativeOffset($("wordList")).left + ($("wordList").getWidth() - $("selectedDefinition").getWidth()) / 2 + "px";
        }
        else {
            $("overlay").hide();
            $("selectedDefinition").hide();
        }
    },
}

/**
 * Handles the HTML control responsible for adding word entries
 */
var WordAddControl = Class.create();

WordAddControl.prototype = {
    controlElem: null,
    wordTextElem: null,
    definitionTextElem: null,
    
    initialize: function(){
        this.controlElem = $("formAddWordDef");
        this.wordTextElem = $("txtWord");
        this.defTextElem = $("txtDef");
        
        $("btnAddWord").observe("click", this.handleAddClick.bindAsEventListener(this));
    },
    
    handleAddClick: function(){
        if (!this.wordTextElem.value.blank()) {
            var word = this.wordTextElem.value;
            var definition = this.defTextElem.value;
            
            wManager.addWordEntry(new WordEntry(word, definition));
            this.clearInputs();
        }
    },
    
    clearInputs: function(){
        this.wordTextElem.value = "";
        this.defTextElem.value = "";
    },
}

/**
 * Words that are deleted are put here so that they can be recuperated as needed
 */
var WasteBin = Class.create();

WasteBin.prototype = {
    items: null,
    wasteElem: null,
    
    initialize: function(){
        this.items = new Hash();
        this.wasteElem = $("wasteBin");
    },
    
    addItem: function(id){
        var wi = new WasteItem(wManager.entries.get(id));
        this.wasteElem.appendChild(wi.itemElem);
        new Draggable(wi.itemElem, {
            revert: true
        });
        
        wManager.deleteWordEntry(wi);
    },
    
    removeItem: function(id){
        this.wasteElem.removeChild($("wasteItem" + id));
    },
    
    clearAll: function(){
    
    },
}

/**
 *
 */
var WasteItem = Class.create();

WasteItem.prototype = {
    itemElem: null,
    wordElem: null,
    
    initialize: function(aWordEntry){
        this.itemElem = copyElementFromTemplate("wasteItemTemplate", "wasteItem" + aWordEntry.id);
        
        this.wordElem = this.itemElem.getElementsByClassName("word")[0];
        
        this.wordElem.update(aWordEntry.word);
    },
}

/**
 *
 *
 */
var CookieStorage = Class.create();

CookieStorage.prototype = {
    storagePeriod: 0, // the period (in days) the word list is saved in a cookie
    cookieName: "",
	autoSaveInterval: 0, // the period (in seconds) the word list is saved automatically in a cookie
    
    initialize: function(storagePeriod, cookieName, autoSaveInterval) {
        this.storagePeriod = storagePeriod;
        this.cookieName = cookieName;
		this.autoSaveInterval = autoSaveInterval;
    },
    
    saveWordList: function(){
        // build the date of expiry for the cookie holding the data
        var expiryDate = new Date();
        expiryDate.setTime(expiryDate.getTime() + (this.storagePeriod * 24 * 60 * 60 * 1000));
        
        // serialize the data - ONLY THE WORD ENTRIES NOT DELETED
		var data = new Array();
		wManager.entries.each(function(hashItem) {
			if (hashItem[1].state != WordEntryStates.DELETED) {
				data.push(hashItem[1]);
			}
		});
		var jsonedData = data.toJSON();
        
        // create the cookie
        document.cookie = this.cookieName + "=" + escape(jsonedData) + "; expires=" + expiryDate.toGMTString() + "; path=/";
    },
    
    loadWordList: function(){
        // get the serialized  data from the cookie
        start = this.cookieName.length + 1;
        end = document.cookie.length;
        var jsonedData = unescape(document.cookie.substring(start, end));
        
        // if there is serialized data, unserialize
        if (jsonedData) {
            var data = jsonedData.evalJSON();
            
            // if the array of unserialized data has elements, rebuild the word list
            if (data.length > 0) {
                // first do some cleanup, empty the wManager and the wList objects
                wManager.cleanup();
                
                // add each and every object form the unserialized array to the wManager one by one
                for (var i = 0; i < data.length; i++) {
                    wManager.addWordEntry(new WordEntry(data[i]));
                }
            }
        }
    },
    
    newWordList: function(){
        // first do some cleanup, empty the wManager and the wList objects
        wManager.cleanup();
        
        wManager.addWordEntry(new WordEntry("adult", "A person who has stopped growing at both ends and is now growing in the middle"));
//        wManager.addWordEntry(new WordEntry("boss", "Someone who is early when you are late and late when you are early"));
//        wManager.addWordEntry(new WordEntry("coffee", "A person who is coughed upon"));
//        wManager.addWordEntry(new WordEntry("dust", "Mud with the juice squeezed out"));
//        wManager.addWordEntry(new WordEntry("eternity", "The last two minutes of a football game"));
//        wManager.addWordEntry(new WordEntry("full name", "What you call your child when you're mad at him/her"));
//        wManager.addWordEntry(new WordEntry("gum", "Adhesive for the hair"));
//        wManager.addWordEntry(new WordEntry("independent", "How we want our children to be as long as they do everything we say"));
//        wManager.addWordEntry(new WordEntry("kissing", "A means of getting two people so close together that they can't see anything wrong with each other"));
//        wManager.addWordEntry(new WordEntry("ow", "The first word spoken by children with older siblings"));
//        wManager.addWordEntry(new WordEntry("psychologist", "A man who watches everyone else when a beautiful girl enters the room"));
//        wManager.addWordEntry(new WordEntry("relief", "What trees do in the spring"));
//        wManager.addWordEntry(new WordEntry("rubberneck", "What you do to relax your wife"));
//        wManager.addWordEntry(new WordEntry("floppy disk", "What you get from trying to carry too much firewood"));
//        wManager.addWordEntry(new WordEntry("hard drive", "Getting home in the winta'"));
//        wManager.addWordEntry(new WordEntry("modem", "What you did to the weeds growing in the driveway"));
//        wManager.addWordEntry(new WordEntry("user", "The neighbor who keeps borrowing stuff"));
    },
    
    clearWordList: function(){
        // first do some cleanup, empty the wManager and the wList objects
        wManager.cleanup();
        
        // remove the data from the cookie by writing in the same cookie a past date and an empty value 
        document.cookie = this.cookieName + "=; expires=-1; path=/";
    },
};


/************* Application start-up *************/
Event.onDOMReady(function(){
	// instantiate the global objects
    wManager = new WordManager();
    wList = new WordList();
    wAddControl = new WordAddControl();
    wBin = new WasteBin();
    selectedDefinition = new SelectedDefinition();
    storage = new CookieStorage(7, "mmwWordList", 30);
    
    // hide the modal dialog that shows the selected word's definition and add click event to the overlay to close the dialog easier
    selectedDefinition.show(false);
    $("overlay").observe("click", (function() {
        selectedDefinition.show(false);
        selectedDefinition.cancelAllEdits();
    }).bindAsEventListener(document));
    
	// prepare drag and drop deletion. make the waste bin a droppable for word list items
    Droppables.add("wasteBin", {
        accept: "wordListItem",
        hoverclass: "wasteBin-active",
        onDrop: function(element) {
            var id = (element.id).replace("wordListItem", "");
            wManager.entries.get(id).setState(WordEntryStates.DELETED);
            wList.handleWordDeleted(id);
            wBin.addItem(id);
        }
    })
    
	// prepare drag and drop undeletion. make the word list a droppable for waste bin items
    Droppables.add("wordList", {
        accept: "wasteItem",
        hoverclass: "wList-active",
        onDrop: function(element){
            var id = (element.id).replace("wasteItem", "");
            wManager.entries.get(id).setState(WordEntryStates.LEARNING);
            wList.handleWordAdded(id);
            wBin.removeItem(id);
        }
    })
    
	// populate the word list either from the storage, if a cookie exists; or from the hard coded defaults
    if (document.cookie) {
        storage.loadWordList();
    }
    else {
        storage.newWordList();
    }
	
	// setup an interval (in milliseconds) to autosave the word list
	setInterval(function() { storage.saveWordList(); }, storage.autoSaveInterval * 1000);
	
	// save the word list when the window unloads (page is closed or refreshed)
	Event.observe(window, "unload", (function() {
		storage.saveWordList();
	}));
});
